#include <stdio.h>
#include <stdint.h>
#include <time.h>
#include <assert.h>
#include <curl/curl.h>

#include "mc.h"
#include "utils.h"
#include "queue.h"
#include "log.h"
#include "libprotocol.h"
#include "confparser.h"

static int mc_conf_hr_delay = 0;

static CONF_INT_CONFIG mc_conf_int_array[] = {
	{"delay", &mc_conf_hr_delay},
	{0, 0}
};

typedef struct token_s {
    char *value;
    size_t length;
} token_t;

#define NREAD_ADD 1
#define NREAD_SET 2
#define NREAD_REPLACE 3
#define NREAD_APPEND 4
#define NREAD_PREPEND 5
#define NREAD_CAS 6

#define COMMAND_TOKEN 0
#define SUBCOMMAND_TOKEN 1
#define KEY_TOKEN 1


#define MAX_TOKENS 8
int protocol_init(char *);
/*
 * Tokenize the command string by replacing whitespace with '\0' and update
 * the token array tokens with pointer to start of each token and length.
 * Returns total number of tokens.  The last valid token is the terminal
 * token (value points to the first unprocessed character of the string and
 * length zero).
 *
 * Usage example:
 *
 *  while(tokenize_command(command, ncommand, tokens, max_tokens) > 0) {
 *      for(int ix = 0; tokens[ix].length != 0; ix++) {
 *          ...
 *      }
 *      ncommand = tokens[ix].value - command;
 *      command  = tokens[ix].value;
 *   }
 */
static size_t
tokenize_command(char *command, token_t *tokens, const size_t max_tokens) {
    char *s, *e;
    size_t ntokens = 0;

    assert(command != NULL && tokens != NULL && max_tokens > 1);

    for (s = e = command; ntokens < max_tokens - 1; ++e) {
        if (*e == ' ') {
            if (s != e) {
                tokens[ntokens].value = s;
                tokens[ntokens].length = e - s;
                ntokens++;
                *e = '\0';
            }
            s = e + 1;
        }
        else if (*e == '\0') {
            if (s != e) {
                tokens[ntokens].value = s;
                tokens[ntokens].length = e - s;
                ntokens++;
            }

            break; /* string end */
        }
    }

    /*
     * If we scanned the whole string, the terminal value pointer is null,
     * otherwise it is the first unprocessed character.
     */
    tokens[ntokens].value =  *e == '\0' ? NULL : e;
    tokens[ntokens].length = 0;
    ntokens++;

    return ntokens;
}

#define out_string(c, b) send_back(c, b, sizeof(b) - 1);


static void
mc_data_cleaner(void *ptr)
{
	sfree(ptr);
	return;
}

static ssize_t 
mc_data_responder(CONN_INFO *cip, void *ptr, int flags)
{
	CSF_UNUSED_ARG(cip);
	CSF_UNUSED_ARG(ptr);
	CSF_UNUSED_ARG(flags);
	/* Write some log here */
	return (RESPONDER_OK|RESPONDER_CLEAN);
}

static int
get_alloc_size(int n)
{
	if (n > 255) {
		return (511);
	} else if (n > 127) {
		return (255);
	} else if (n > 63) {
		return (127);
	} else if (n > 31) {
		return (63);
	} else if (n > 15) {
		return (31);
	} else if (n > 7) {
		return (15);
	} else if (n > 3) {
		return (7);
	} else {
		return (3);
	}
}

static int 
process_set_command(CONN_INFO *c, token_t *tokens,
	const size_t ntokens, int comm, MC_STATE *msp) {
    char *key;
    size_t nkey;
    int flags;
    time_t exptime;
    int vlen;
    MC_DATA *mc_data;

	CSF_UNUSED_ARG(ntokens);
	CSF_UNUSED_ARG(comm);

    if (tokens[KEY_TOKEN].length > KEY_MAX_LENGTH) {
        out_string(c, "CLIENT_ERROR bad command line format");
		return (PROTOCOL_ERROR);
    }

    key = tokens[KEY_TOKEN].value;
    nkey = tokens[KEY_TOKEN].length;

    flags = strtoul(tokens[2].value, NULL, 10);
    exptime = strtol(tokens[3].value, NULL, 10);
    vlen = strtol(tokens[4].value, NULL, 10);

	if (vlen > (VALUE_MAX_LENGTH - 2)) {
		msp->est = vlen + 2;
		msp->state = mc_state_sallow;
		out_string(c, "SERVER_ERROR object too large for cache");
		return (PROTOCOL_OK);
	}

    mc_data = (MC_DATA *)smalloc(sizeof(MC_DATA));

	
    strlcpy(mc_data->key, key, nkey + 1);
    mc_data->key_len = nkey;
	mc_data->value_len = 0;
	mc_data->delay = mc_conf_hr_delay;
	msp->est = vlen + 2;
	msp->state = mc_state_read;
	msp->mdp = mc_data;

	return (PROTOCOL_OK);
}

/* ntokens is overwritten here... shrug.. */
static inline void
process_get_command(CONN_INFO *c, token_t *tokens, size_t ntokens) {
    char *key;
    size_t nkey;
    int i = 0;
    token_t *key_token = &tokens[KEY_TOKEN];
    char *suffix;
    assert(c != NULL);

    return;
}


static int
process_command(CONN_INFO *c, char *command, MC_STATE *msp) {

    token_t tokens[MAX_TOKENS];
    size_t ntokens;
    int comm;

    /*
     * for commands set/add/replace, we build an item and read the data
     * directly into it, then continue in nread_complete().
     */

    ntokens = tokenize_command(command, tokens, MAX_TOKENS);

	if ((ntokens == 6 || ntokens == 7) &&
               ((strcmp(tokens[COMMAND_TOKEN].value, "set") == 0 && (comm = NREAD_SET)))) {

        process_set_command(c, tokens, ntokens, comm, msp);
    } else if (ntokens == 2 && (strcmp(tokens[COMMAND_TOKEN].value, "quit") == 0)) {
	return (-2);
	} else {
    	msp->est = 0;
		msp->state = mc_state_init;
        out_string(c, "ERROR");
		return -1;
    }
    return 0;
}

static int
mc_session_entry(void *csp, CONN_INFO *cip, void *s, void *data, int len)
{
	char *el, *cont;
	char *value;
	int val_len;
	int rc;
	char *str = data;
	int64_t	tid;

	MC_STATE *msp = (MC_STATE *)s;

	/* it means a init set command */
	if (msp->state == mc_state_init) {
		if (len <= 3)
			return 0;
	    el = memchr(str, '\n', len);
	    if (!el) {
			out_string(cip, "SERVER_ERROR parse error!\r\n");
	        return 0;
	    }
	    cont = el + 1;
	    if ((el - str) > 1 && *(el - 1) == '\r') {
	        el--;
	    }
	    *el = '\0';	
		rc = process_command(cip, str, msp);
		if (rc == -1)
			return (PROTOCOL_OK);
		else if (rc == -2)
			return (PROTOCOL_DISCONNECT);
		value = el+2;
		val_len = len -(value - str);
	} else if (msp->state == mc_state_read) {
		value = str;
		val_len = len;
	} 

	if (msp->state == mc_state_sallow) {
		msp->est -= val_len;
		if (msp->est == 0)
			msp->state = mc_state_init;
		return (PROTOCOL_OK);
	}
	
	strlcpy(msp->mdp->value + msp->mdp->value_len, value, val_len + 1);
	msp->mdp->value_len += val_len;
	msp->est -= val_len;

	if (msp->est == 0) {
		tid = submit_request(0, csp, msp->mdp, 
			mc_data_cleaner, mc_data_responder, REQUEST_NONE);

		msp->mdp = NULL;
		msp->state = mc_state_init;
		if (tid >= 0) {
			WLOG_INFO("txn_id: %lld",
				(long long int)tid);
			out_string(cip, "STORED\r\n");
			return (PROTOCOL_OK);
		} else {
			WLOG_ERR("Submit FAILED!");
			out_string(cip, "SERVER_ERROR Submit Failed!\r\n");
			mc_data_cleaner(msp->mdp);
			return (PROTOCOL_ERROR);
		}
	}

	return (PROTOCOL_OK);
}

static void *
mc_session_start(CONN_INFO *cip)
{
	MC_STATE *msp;

	CSF_UNUSED_ARG(cip);

	msp = (MC_STATE *)smalloc(sizeof(MC_STATE));

	msp->est = 0;
	msp->state = mc_state_init;
	msp->mdp = NULL;
	return msp;
}

static void
mc_session_end(CONN_INFO *cip, void *ptr)
{
	CSF_UNUSED_ARG(cip);
	MC_STATE *msp;

	msp = (MC_STATE *)ptr;

	if (msp->mdp)
		sfree(msp->mdp);
	
	sfree(msp);
	
}

int
protocol_init(char *proto_name)
{
	int rc;

	set_protocol_session_start(mc_session_start);
	set_protocol_session_entry(mc_session_entry);
	set_protocol_session_end(mc_session_end);

	rc = load_conf(NULL, proto_name, mc_conf_int_array, NULL);
	if (rc < 0) {
		WLOG_ERR("MC conf_load error!");
		return (PROTOCOL_ERROR);
	}

	curl_global_init(CURL_GLOBAL_ALL);
	return (PROTOCOL_OK);
}
